home *** CD-ROM | disk | FTP | other *** search
/ PC Advisor 2010 April / PCA177.iso / ESSENTIALS / Firefox Setup.exe / nonlocalized / components / nsUrlClassifierListManager.js < prev    next >
Encoding:
Text File  |  2009-07-15  |  19.6 KB  |  616 lines

  1. //@line 37 "e:\builds\moz2_slave\win32_build\build\toolkit\components\url-classifier\src\nsUrlClassifierListManager.js"
  2.  
  3. const Cc = Components.classes;
  4. const Ci = Components.interfaces;
  5.  
  6. //@line 37 "e:\builds\moz2_slave\win32_build\build\toolkit\components\url-classifier\content\listmanager.js"
  7.  
  8.  
  9. // A class that manages lists, namely white and black lists for
  10. // phishing or malware protection. The ListManager knows how to fetch,
  11. // update, and store lists.
  12. //
  13. // There is a single listmanager for the whole application.
  14. //
  15. // TODO more comprehensive update tests, for example add unittest check 
  16. //      that the listmanagers tables are properly written on updates
  17.  
  18. // How frequently we check for updates (30 minutes)
  19. const kUpdateInterval = 30 * 60 * 1000;
  20.  
  21. function QueryAdapter(callback) {
  22.   this.callback_ = callback;
  23. };
  24.  
  25. QueryAdapter.prototype.handleResponse = function(value) {
  26.   this.callback_.handleEvent(value);
  27. }
  28.  
  29. /**
  30.  * A ListManager keeps track of black and white lists and knows
  31.  * how to update them.
  32.  *
  33.  * @constructor
  34.  */
  35. function PROT_ListManager() {
  36.   this.debugZone = "listmanager";
  37.   G_debugService.enableZone(this.debugZone);
  38.  
  39.   this.currentUpdateChecker_ = null;   // set when we toggle updates
  40.   this.prefs_ = new G_Preferences();
  41.  
  42.   this.updateserverURL_ = null;
  43.   this.gethashURL_ = null;
  44.  
  45.   this.isTesting_ = false;
  46.  
  47.   this.tablesData = {};
  48.  
  49.   this.observerServiceObserver_ = new G_ObserverServiceObserver(
  50.                                           'quit-application',
  51.                                           BindToObject(this.shutdown_, this),
  52.                                           true /*only once*/);
  53.  
  54.   // Lazily create the key manager (to avoid fetching keys when they
  55.   // aren't needed).
  56.   this.keyManager_ = null;
  57.  
  58.   this.rekeyObserver_ = new G_ObserverServiceObserver(
  59.                                           'url-classifier-rekey-requested',
  60.                                           BindToObject(this.rekey_, this),
  61.                                           false);
  62.   this.updateWaitingForKey_ = false;
  63.  
  64.   this.cookieObserver_ = new G_ObserverServiceObserver(
  65.                                           'cookie-changed',
  66.                                           BindToObject(this.cookieChanged_, this),
  67.                                           false);
  68.  
  69.   /* Backoff interval should be between 30 and 60 minutes. */
  70.   var backoffInterval = 30 * 60 * 1000;
  71.   backoffInterval += Math.floor(Math.random() * (30 * 60 * 1000));
  72.  
  73.   this.requestBackoff_ = new RequestBackoff(2 /* max errors */,
  74.                                       60*1000 /* retry interval, 1 min */,
  75.                                             4 /* num requests */,
  76.                                    60*60*1000 /* request time, 60 min */,
  77.                               backoffInterval /* backoff interval, 60 min */,
  78.                                  8*60*60*1000 /* max backoff, 8hr */);
  79.  
  80.   this.dbService_ = Cc["@mozilla.org/url-classifier/dbservice;1"]
  81.                    .getService(Ci.nsIUrlClassifierDBService);
  82.  
  83.   this.hashCompleter_ = Cc["@mozilla.org/url-classifier/hashcompleter;1"]
  84.                         .getService(Ci.nsIUrlClassifierHashCompleter);
  85. }
  86.  
  87. /**
  88.  * xpcom-shutdown callback
  89.  * Delete all of our data tables which seem to leak otherwise.
  90.  */
  91. PROT_ListManager.prototype.shutdown_ = function() {
  92.   if (this.keyManager_) {
  93.     this.keyManager_.shutdown();
  94.   }
  95.  
  96.   for (var name in this.tablesData) {
  97.     delete this.tablesData[name];
  98.   }
  99. }
  100.  
  101. /**
  102.  * Set the url we check for updates.  If the new url is valid and different,
  103.  * update our table list.
  104.  * 
  105.  * After setting the update url, the caller is responsible for registering
  106.  * tables and then toggling update checking.  All the code for this logic is
  107.  * currently in browser/components/safebrowsing.  Maybe it should be part of
  108.  * the listmanger?
  109.  */
  110. PROT_ListManager.prototype.setUpdateUrl = function(url) {
  111.   G_Debug(this, "Set update url: " + url);
  112.   if (url != this.updateserverURL_) {
  113.     this.updateserverURL_ = url;
  114.     this.requestBackoff_.reset();
  115.     
  116.     // Remove old tables which probably aren't valid for the new provider.
  117.     for (var name in this.tablesData) {
  118.       delete this.tablesData[name];
  119.     }
  120.   }
  121. }
  122.  
  123. /**
  124.  * Set the gethash url.
  125.  */
  126. PROT_ListManager.prototype.setGethashUrl = function(url) {
  127.   G_Debug(this, "Set gethash url: " + url);
  128.   if (url != this.gethashURL_) {
  129.     this.gethashURL_ = url;
  130.     this.hashCompleter_.gethashUrl = url;
  131.   }
  132. }
  133.  
  134. /**
  135.  * Set the crypto key url.
  136.  * @param url String
  137.  */
  138. PROT_ListManager.prototype.setKeyUrl = function(url) {
  139.   G_Debug(this, "Set key url: " + url);
  140.   if (!this.keyManager_) {
  141.     this.keyManager_ = new PROT_UrlCryptoKeyManager();
  142.     this.keyManager_.onNewKey(BindToObject(this.newKey_, this));
  143.  
  144.     this.hashCompleter_.setKeys(this.keyManager_.getClientKey(),
  145.                                 this.keyManager_.getWrappedKey());
  146.   }
  147.  
  148.   this.keyManager_.setKeyUrl(url);
  149. }
  150.  
  151. /**
  152.  * Register a new table table
  153.  * @param tableName - the name of the table
  154.  * @param opt_requireMac true if a mac is required on update, false otherwise
  155.  * @returns true if the table could be created; false otherwise
  156.  */
  157. PROT_ListManager.prototype.registerTable = function(tableName, 
  158.                                                     opt_requireMac) {
  159.   this.tablesData[tableName] = {};
  160.   this.tablesData[tableName].needsUpdate = false;
  161.  
  162.   return true;
  163. }
  164.  
  165. /**
  166.  * Enable updates for some tables
  167.  * @param tables - an array of table names that need updating
  168.  */
  169. PROT_ListManager.prototype.enableUpdate = function(tableName) {
  170.   var changed = false;
  171.   var table = this.tablesData[tableName];
  172.   if (table) {
  173.     G_Debug(this, "Enabling table updates for " + tableName);
  174.     table.needsUpdate = true;
  175.     changed = true;
  176.   }
  177.  
  178.   if (changed === true)
  179.     this.maybeToggleUpdateChecking();
  180. }
  181.  
  182. /**
  183.  * Disables updates for some tables
  184.  * @param tables - an array of table names that no longer need updating
  185.  */
  186. PROT_ListManager.prototype.disableUpdate = function(tableName) {
  187.   var changed = false;
  188.   var table = this.tablesData[tableName];
  189.   if (table) {
  190.     G_Debug(this, "Disabling table updates for " + tableName);
  191.     table.needsUpdate = false;
  192.     changed = true;
  193.   }
  194.  
  195.   if (changed === true)
  196.     this.maybeToggleUpdateChecking();
  197. }
  198.  
  199. /**
  200.  * Determine if we have some tables that need updating.
  201.  */
  202. PROT_ListManager.prototype.requireTableUpdates = function() {
  203.   for (var type in this.tablesData) {
  204.     // Tables that need updating even if other tables dont require it
  205.     if (this.tablesData[type].needsUpdate)
  206.       return true;
  207.   }
  208.  
  209.   return false;
  210. }
  211.  
  212. /**
  213.  * Start managing the lists we know about. We don't do this automatically
  214.  * when the listmanager is instantiated because their profile directory
  215.  * (where we store the lists) might not be available.
  216.  */
  217. PROT_ListManager.prototype.maybeStartManagingUpdates = function() {
  218.   if (this.isTesting_)
  219.     return;
  220.  
  221.   // We might have been told about tables already, so see if we should be
  222.   // actually updating.
  223.   this.maybeToggleUpdateChecking();
  224. }
  225.  
  226. PROT_ListManager.prototype.kickoffUpdate_ = function (tableData)
  227. {
  228.   this.startingUpdate_ = false;
  229.   // If the user has never downloaded tables, do the check now.
  230.   // If the user has tables, add a fuzz of a few minutes.
  231.   var initialUpdateDelay = 3000;
  232.   if (tableData != "") {
  233.     // Add a fuzz of 0-5 minutes.
  234.     initialUpdateDelay += Math.floor(Math.random() * (5 * 60 * 1000));
  235.   }
  236.  
  237.   this.currentUpdateChecker_ =
  238.     new G_Alarm(BindToObject(this.checkForUpdates, this),
  239.                 initialUpdateDelay);
  240. }
  241.  
  242. /**
  243.  * Determine if we have any tables that require updating.  Different
  244.  * Wardens may call us with new tables that need to be updated.
  245.  */ 
  246. PROT_ListManager.prototype.maybeToggleUpdateChecking = function() {
  247.   // If we are testing or dont have an application directory yet, we should
  248.   // not start reading tables from disk or schedule remote updates
  249.   if (this.isTesting_)
  250.     return;
  251.  
  252.   // We update tables if we have some tables that want updates.  If there
  253.   // are no tables that want to be updated - we dont need to check anything.
  254.   if (this.requireTableUpdates() === true) {
  255.     G_Debug(this, "Starting managing lists");
  256.     this.startUpdateChecker();
  257.  
  258.     // Multiple warden can ask us to reenable updates at the same time, but we
  259.     // really just need to schedule a single update.
  260.     if (!this.currentUpdateChecker && !this.startingUpdate_) {
  261.       this.startingUpdate_ = true;
  262.       // check the current state of tables in the database
  263.       this.dbService_.getTables(BindToObject(this.kickoffUpdate_, this));
  264.     }
  265.   } else {
  266.     G_Debug(this, "Stopping managing lists (if currently active)");
  267.     this.stopUpdateChecker();                    // Cancel pending updates
  268.   }
  269. }
  270.  
  271. /**
  272.  * Start periodic checks for updates. Idempotent.
  273.  * We want to distribute update checks evenly across the update period (an
  274.  * hour).  To do this, we pick a random number of time between 0 and 30
  275.  * minutes.  The client first checks at 15 + rand, then every 30 minutes after
  276.  * that.
  277.  */
  278. PROT_ListManager.prototype.startUpdateChecker = function() {
  279.   this.stopUpdateChecker();
  280.   
  281.   // Schedule the first check for between 15 and 45 minutes.
  282.   var repeatingUpdateDelay = kUpdateInterval / 2;
  283.   repeatingUpdateDelay += Math.floor(Math.random() * kUpdateInterval);
  284.   this.updateChecker_ = new G_Alarm(BindToObject(this.initialUpdateCheck_,
  285.                                                  this),
  286.                                     repeatingUpdateDelay);
  287. }
  288.  
  289. /**
  290.  * Callback for the first update check.
  291.  * We go ahead and check for table updates, then start a regular timer (once
  292.  * every 30 minutes).
  293.  */
  294. PROT_ListManager.prototype.initialUpdateCheck_ = function() {
  295.   this.checkForUpdates();
  296.   this.updateChecker_ = new G_Alarm(BindToObject(this.checkForUpdates, this), 
  297.                                     kUpdateInterval, true /* repeat */);
  298. }
  299.  
  300. /**
  301.  * Stop checking for updates. Idempotent.
  302.  */
  303. PROT_ListManager.prototype.stopUpdateChecker = function() {
  304.   if (this.updateChecker_) {
  305.     this.updateChecker_.cancel();
  306.     this.updateChecker_ = null;
  307.   }
  308.   // Cancel the oneoff check from maybeToggleUpdateChecking.
  309.   if (this.currentUpdateChecker_) {
  310.     this.currentUpdateChecker_.cancel();
  311.     this.currentUpdateChecker_ = null;
  312.   }
  313. }
  314.  
  315. /**
  316.  * Provides an exception free way to look up the data in a table. We
  317.  * use this because at certain points our tables might not be loaded,
  318.  * and querying them could throw.
  319.  *
  320.  * @param table String Name of the table that we want to consult
  321.  * @param key String Key for table lookup
  322.  * @param callback nsIUrlListManagerCallback (ie., Function) given false or the
  323.  *        value in the table corresponding to key.  If the table name does not
  324.  *        exist, we return false, too.
  325.  */
  326. PROT_ListManager.prototype.safeLookup = function(key, callback) {
  327.   try {
  328.     G_Debug(this, "safeLookup: " + key);
  329.     var cb = new QueryAdapter(callback);
  330.     this.dbService_.lookup(key,
  331.                            BindToObject(cb.handleResponse, cb),
  332.                            true);
  333.   } catch(e) {
  334.     G_Debug(this, "safeLookup masked failure for key " + key + ": " + e);
  335.     callback.handleEvent("");
  336.   }
  337. }
  338.  
  339. /**
  340.  * Updates our internal tables from the update server
  341.  *
  342.  * @returns true when a new request was scheduled, false if an old request
  343.  *          was still pending.
  344.  */
  345. PROT_ListManager.prototype.checkForUpdates = function() {
  346.   // Allow new updates to be scheduled from maybeToggleUpdateChecking()
  347.   this.currentUpdateChecker_ = null;
  348.  
  349.   if (!this.updateserverURL_) {
  350.     G_Debug(this, 'checkForUpdates: no update server url');
  351.     return false;
  352.   }
  353.  
  354.   // See if we've triggered the request backoff logic.
  355.   if (!this.requestBackoff_.canMakeRequest())
  356.     return false;
  357.  
  358.   // Grab the current state of the tables from the database
  359.   this.dbService_.getTables(BindToObject(this.makeUpdateRequest_, this));
  360.   return true;
  361. }
  362.  
  363. /**
  364.  * Method that fires the actual HTTP update request.
  365.  * First we reset any tables that have disappeared.
  366.  * @param tableData List of table data already in the database, in the form
  367.  *        tablename;<chunk ranges>\n
  368.  */
  369. PROT_ListManager.prototype.makeUpdateRequest_ = function(tableData) {
  370.   if (!this.keyManager_)
  371.     return;
  372.  
  373.   if (!this.keyManager_.hasKey()) {
  374.     // We don't have a client key yet.  Schedule a rekey, and rerequest
  375.     // when we have one.
  376.  
  377.     // If there's already an update waiting for a new key, don't bother.
  378.     if (this.updateWaitingForKey_)
  379.       return;
  380.  
  381.     // If maybeReKey() returns false we have asked for too many keys,
  382.     // and won't be getting a new one.  Since we don't want to do
  383.     // updates without a client key, we'll skip this update if maybeReKey()
  384.     // fails.
  385.     if (this.keyManager_.maybeReKey())
  386.       this.updateWaitingForKey_ = true;
  387.  
  388.     return;
  389.   }
  390.  
  391.   var tableList;
  392.   var tableNames = {};
  393.   for (var tableName in this.tablesData) {
  394.     if (this.tablesData[tableName].needsUpdate)
  395.       tableNames[tableName] = true;
  396.     if (!tableList) {
  397.       tableList = tableName;
  398.     } else {
  399.       tableList += "," + tableName;
  400.     }
  401.   }
  402.  
  403.   var request = "";
  404.  
  405.   // For each table already in the database, include the chunk data from
  406.   // the database
  407.   var lines = tableData.split("\n");
  408.   for (var i = 0; i < lines.length; i++) {
  409.     var fields = lines[i].split(";");
  410.     if (tableNames[fields[0]]) {
  411.       request += lines[i] + ":mac\n";
  412.       delete tableNames[fields[0]];
  413.     }
  414.   }
  415.  
  416.   // For each requested table that didn't have chunk data in the database,
  417.   // request it fresh
  418.   for (var tableName in tableNames) {
  419.     request += tableName + ";mac\n";
  420.   }
  421.  
  422.   G_Debug(this, 'checkForUpdates: scheduling request..');
  423.   var streamer = Cc["@mozilla.org/url-classifier/streamupdater;1"]
  424.                  .getService(Ci.nsIUrlClassifierStreamUpdater);
  425.   try {
  426.     streamer.updateUrl = this.updateserverURL_ +
  427.                          "&wrkey=" + this.keyManager_.getWrappedKey();
  428.   } catch (e) {
  429.     G_Debug(this, 'invalid url');
  430.     return;
  431.   }
  432.  
  433.   this.requestBackoff_.noteRequest();
  434.  
  435.   if (!streamer.downloadUpdates(tableList,
  436.                                 request,
  437.                                 this.keyManager_.getClientKey(),
  438.                                 BindToObject(this.updateSuccess_, this),
  439.                                 BindToObject(this.updateError_, this),
  440.                                 BindToObject(this.downloadError_, this))) {
  441.     G_Debug(this, "pending update, wait until later");
  442.   }
  443. }
  444.  
  445. /**
  446.  * Callback function if the update request succeeded.
  447.  * @param waitForUpdate String The number of seconds that the client should
  448.  *        wait before requesting again.
  449.  */
  450. PROT_ListManager.prototype.updateSuccess_ = function(waitForUpdate) {
  451.   G_Debug(this, "update success: " + waitForUpdate);
  452.   if (waitForUpdate) {
  453.     var delay = parseInt(waitForUpdate, 10);
  454.     // As long as the delay is something sane (5 minutes or more), update
  455.     // our delay time for requesting updates
  456.     if (delay >= (5 * 60) && this.updateChecker_)
  457.       this.updateChecker_.setDelay(delay * 1000);
  458.   }
  459.  
  460.   // Let the backoff object know that we completed successfully.
  461.   this.requestBackoff_.noteServerResponse(200);
  462. }
  463.  
  464. /**
  465.  * Callback function if the update request succeeded.
  466.  * @param result String The error code of the failure
  467.  */
  468. PROT_ListManager.prototype.updateError_ = function(result) {
  469.   G_Debug(this, "update error: " + result);
  470.   // XXX: there was some trouble applying the updates.
  471. }
  472.  
  473. /**
  474.  * Callback function when the download failed
  475.  * @param status String http status or an empty string if connection refused.
  476.  */
  477. PROT_ListManager.prototype.downloadError_ = function(status) {
  478.   G_Debug(this, "download error: " + status);
  479.   // If status is empty, then we assume that we got an NS_CONNECTION_REFUSED
  480.   // error.  In this case, we treat this is a http 500 error.
  481.   if (!status) {
  482.     status = 500;
  483.   }
  484.   status = parseInt(status, 10);
  485.   this.requestBackoff_.noteServerResponse(status);
  486.  
  487.   if (this.requestBackoff_.isErrorStatus(status)) {
  488.     // Schedule an update for when our backoff is complete
  489.     this.currentUpdateChecker_ =
  490.       new G_Alarm(BindToObject(this.checkForUpdates, this),
  491.                   this.requestBackoff_.nextRequestDelay());
  492.   }
  493. }
  494.  
  495. /**
  496.  * Called when either the update process or a gethash request signals
  497.  * that the server requested a rekey.
  498.  */
  499. PROT_ListManager.prototype.rekey_ = function() {
  500.   G_Debug(this, "rekey requested");
  501.  
  502.   // The current key is no good anymore.
  503.   this.keyManager_.dropKey();
  504.   this.keyManager_.maybeReKey();
  505. }
  506.  
  507. /**
  508.  * Called when cookies are cleared - clears the current MAC keys.
  509.  */
  510. PROT_ListManager.prototype.cookieChanged_ = function(subject, topic, data) {
  511.   if (data != "cleared")
  512.     return;
  513.  
  514.   G_Debug(this, "cookies cleared");
  515.   this.keyManager_.dropKey();
  516. }
  517.  
  518. /**
  519.  * Called when we've received a new key from the server.
  520.  */
  521. PROT_ListManager.prototype.newKey_ = function() {
  522.   G_Debug(this, "got a new MAC key");
  523.  
  524.   this.hashCompleter_.setKeys(this.keyManager_.getClientKey(),
  525.                               this.keyManager_.getWrappedKey());
  526.  
  527.   if (this.keyManager_.hasKey()) {
  528.     if (this.updateWaitingForKey_) {
  529.       this.updateWaitingForKey_ = false;
  530.       this.checkForUpdates();
  531.     }
  532.   }
  533. }
  534.  
  535. PROT_ListManager.prototype.QueryInterface = function(iid) {
  536.   if (iid.equals(Ci.nsISupports) ||
  537.       iid.equals(Ci.nsIUrlListManager) ||
  538.       iid.equals(Ci.nsITimerCallback))
  539.     return this;
  540.  
  541.   Components.returnCode = Components.results.NS_ERROR_NO_INTERFACE;
  542.   return null;
  543. }
  544. //@line 42 "e:\builds\moz2_slave\win32_build\build\toolkit\components\url-classifier\src\nsUrlClassifierListManager.js"
  545.  
  546. var modScope = this;
  547. function Init() {
  548.   // Pull the library in.
  549.   var jslib = Cc["@mozilla.org/url-classifier/jslib;1"]
  550.               .getService().wrappedJSObject;
  551.   Function.prototype.inherits = jslib.Function.prototype.inherits;
  552.   modScope.G_Preferences = jslib.G_Preferences;
  553.   modScope.G_PreferenceObserver = jslib.G_PreferenceObserver;
  554.   modScope.G_ObserverServiceObserver = jslib.G_ObserverServiceObserver;
  555.   modScope.G_Debug = jslib.G_Debug;
  556.   modScope.G_Assert = jslib.G_Assert;
  557.   modScope.G_debugService = jslib.G_debugService;
  558.   modScope.G_Alarm = jslib.G_Alarm;
  559.   modScope.BindToObject = jslib.BindToObject;
  560.   modScope.PROT_XMLFetcher = jslib.PROT_XMLFetcher;
  561.   modScope.PROT_UrlCryptoKeyManager = jslib.PROT_UrlCryptoKeyManager;
  562.   modScope.RequestBackoff = jslib.RequestBackoff;
  563.  
  564.   // We only need to call Init once.
  565.   modScope.Init = function() {};
  566. }
  567.  
  568. // Module object
  569. function UrlClassifierListManagerMod() {
  570.   this.firstTime = true;
  571.   this.cid = Components.ID("{ca168834-cc00-48f9-b83c-fd018e58cae3}");
  572.   this.progid = "@mozilla.org/url-classifier/listmanager;1";
  573. }
  574.  
  575. UrlClassifierListManagerMod.prototype.registerSelf = function(compMgr, fileSpec, loc, type) {
  576.   if (this.firstTime) {
  577.     this.firstTime = false;
  578.     throw Components.results.NS_ERROR_FACTORY_REGISTER_AGAIN;
  579.   }
  580.   compMgr = compMgr.QueryInterface(Ci.nsIComponentRegistrar);
  581.   compMgr.registerFactoryLocation(this.cid,
  582.                                   "UrlClassifier List Manager Module",
  583.                                   this.progid,
  584.                                   fileSpec,
  585.                                   loc,
  586.                                   type);
  587. };
  588.  
  589. UrlClassifierListManagerMod.prototype.getClassObject = function(compMgr, cid, iid) {  
  590.   if (!cid.equals(this.cid))
  591.     throw Components.results.NS_ERROR_NO_INTERFACE;
  592.   if (!iid.equals(Ci.nsIFactory))
  593.     throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  594.  
  595.   return this.factory;
  596. }
  597.  
  598. UrlClassifierListManagerMod.prototype.canUnload = function(compMgr) {
  599.   return true;
  600. }
  601.  
  602. UrlClassifierListManagerMod.prototype.factory = {
  603.   createInstance: function(outer, iid) {
  604.     if (outer != null)
  605.       throw Components.results.NS_ERROR_NO_AGGREGATION;
  606.     Init();
  607.     return (new PROT_ListManager()).QueryInterface(iid);
  608.   }
  609. };
  610.  
  611. var ListManagerModInst = new UrlClassifierListManagerMod();
  612.  
  613. function NSGetModule(compMgr, fileSpec) {
  614.   return ListManagerModInst;
  615. }
  616.